//-
// ==========================================================================
// Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors.  All 
// rights reserved.
//
// The coded instructions, statements, computer programs, and/or related 
// material (collectively the "Data") in these files contain unpublished 
// information proprietary to Autodesk, Inc. ("Autodesk") and/or its 
// licensors, which is protected by U.S. and Canadian federal copyright 
// law and by international treaties.
//
// The Data is provided for use exclusively by You. You have the right 
// to use, modify, and incorporate this Data into other products for 
// purposes authorized by the Autodesk software license agreement, 
// without fee.
//
// The copyright notices in the Software and this entire statement, 
// including the above license grant, this restriction and the 
// following disclaimer, must be included in all copies of the 
// Software, in whole or in part, and all derivative works of 
// the Software, unless such copies or derivative works are solely 
// in the form of machine-executable object code generated by a 
// source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. 
// AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED 
// WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF 
// NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 
// PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR 
// TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS 
// BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL, 
// DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK 
// AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY 
// OR PROBABILITY OF SUCH DAMAGES.
//
// ==========================================================================
//+

////////////////////////////////////////////////////////////////////////
//
// helixTool.cpp
//
// Description:
//     Interactive tool to draw a helix.
//     Uses OpenGL to draw a guideline for the helix.
//
//
// Steps in creating a tool command
//
// 1. Create a tool command class. 
//    Same process as an MPxCommand except define 2 methods
//    for interactive use: cancel, and finalize.
//    There is also an addition constructor MPxToolCommand(), which 
//    is called from your context when the command needs to be invoked.
//
// 2. Define your context.
//    This is accomplished by deriving off of MPxContext and overriding
//    whatever methods you need.
//
// 3. Create a command class to create your context.
//    You will call this command in Maya to create and name a context.
//
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <maya/MIOStream.h>
#include <math.h>

#include <maya/MString.h>
#include <maya/MArgList.h>
#include <maya/MEvent.h>
#include <maya/MGlobal.h>
#include <maya/M3dView.h>
#include <maya/MPoint.h>
#include <maya/MPointArray.h>
#include <maya/MDoubleArray.h>
#include <maya/MDagPath.h>

#include <maya/MPxContext.h>
#include <maya/MPxContextCommand.h>
#include <maya/MPxToolCommand.h> 
#include <maya/MToolsInfo.h>

#include <maya/MFnPlugin.h>
#include <maya/MFnNurbsCurve.h> 

#include <maya/MSyntax.h>
#include <maya/MArgParser.h>
#include <maya/MArgDatabase.h>
#include <maya/MCursor.h>

#if defined(OSMac_MachO_)
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#else
#include <GL/gl.h>
#include <GL/glu.h>
#endif

#define kPitchFlag			"-p"
#define kPitchFlagLong		"-pitch"
#define kRadiusFlag			"-r"
#define kRadiusFlagLong		"-radius"
#define kNumberCVsFlag		"-ncv"
#define kNumberCVsFlagLong	"-numCVs"
#define kUpsideDownFlag		"-ud"
#define kUpsideDownFlagLong	"-upsideDown"

/////////////////////////////////////////////////////////////
// The users tool command
/////////////////////////////////////////////////////////////

#define		NUMBER_OF_CVS		20

class helixTool : public MPxToolCommand
{
public:
					helixTool(); 
	virtual			~helixTool(); 
	static void*	creator();

	MStatus			doIt(const MArgList& args);
	MStatus			parseArgs(const MArgList& args);
	MStatus			redoIt();
	MStatus			undoIt();
	bool			isUndoable() const;
	MStatus			finalize();
	static MSyntax	newSyntax();
	
	void			setRadius(double newRadius);
	void			setPitch(double newPitch);
	void			setNumCVs(unsigned newNumCVs);
	void			setUpsideDown(bool newUpsideDown);

private:
	double			radius;     	// Helix radius
	double			pitch;      	// Helix pitch
	unsigned		numCV;			// Helix number of CVs
	bool			upDown;			// Helix upsideDown
	MDagPath		path;			// The dag path to the curve.
									// Don't save the pointer!
};


void* helixTool::creator()
{
	return new helixTool;
}

helixTool::~helixTool() {}

helixTool::helixTool()
{
	numCV = 20;
	upDown = false;
	setCommandString("helixToolCmd");
}
	
MSyntax helixTool::newSyntax()
{
	MSyntax syntax;

	syntax.addFlag(kPitchFlag, kPitchFlagLong, MSyntax::kDouble);
	syntax.addFlag(kRadiusFlag, kRadiusFlagLong, MSyntax::kDouble);
	syntax.addFlag(kNumberCVsFlag, kNumberCVsFlagLong, MSyntax::kUnsigned);
	syntax.addFlag(kUpsideDownFlag, kUpsideDownFlagLong, MSyntax::kBoolean);
	
	return syntax;
}

MStatus helixTool::doIt(const MArgList &args)
//
// Description
//     Sets up the helix parameters from arguments passed to the
//     MEL command.
//
{
	MStatus status;

	status = parseArgs(args);

	if (MS::kSuccess != status)
		return status;

	return redoIt();
}

MStatus helixTool::parseArgs(const MArgList &args)
{
	MStatus status;
	MArgDatabase argData(syntax(), args);

	if (argData.isFlagSet(kPitchFlag)) {
		double tmp;
		status = argData.getFlagArgument(kPitchFlag, 0, tmp);
		if (!status) {
			status.perror("pitch flag parsing failed");
			return status;
		}
		pitch = tmp;
	}
	
	if (argData.isFlagSet(kRadiusFlag)) {
		double tmp;
		status = argData.getFlagArgument(kRadiusFlag, 0, tmp);
		if (!status) {
			status.perror("radius flag parsing failed");
			return status;
		}
		radius = tmp;
	}
	
	if (argData.isFlagSet(kNumberCVsFlag)) {
		unsigned tmp;
		status = argData.getFlagArgument(kNumberCVsFlag, 0, tmp);
		if (!status) {
			status.perror("numCVs flag parsing failed");
			return status;
		}
		numCV = tmp;
	}
	
	if (argData.isFlagSet(kUpsideDownFlag)) {
		bool tmp;
		status = argData.getFlagArgument(kUpsideDownFlag, 0, tmp);
		if (!status) {
			status.perror("upside down flag parsing failed");
			return status;
		}
		upDown = tmp;
	}

	return MS::kSuccess;
}	


MStatus helixTool::redoIt()
//
// Description
//     This method creates the helix curve from the
//     pitch and radius values
//
{
	MStatus stat;

	const unsigned  deg     = 3;            // Curve Degree
	const unsigned  ncvs    = numCV;		// Number of CVs
	const unsigned  spans   = ncvs - deg;   // Number of spans
	const unsigned  nknots  = spans+2*deg-1;// Number of knots
	unsigned	    i;
	MPointArray		controlVertices;
	MDoubleArray	knotSequences;

	int upFactor;
	if (upDown) upFactor = -1;
	else upFactor = 1;

	// Set up cvs and knots for the helix
	//
	for (i = 0; i < ncvs; i++)
		controlVertices.append(MPoint(radius * cos((double) i),
									  upFactor * pitch * (double) i, 
									  radius * sin((double) i)));

	for (i = 0; i < nknots; i++)
		knotSequences.append((double) i);

	// Now create the curve
	//
	MFnNurbsCurve curveFn;

	curveFn.create(controlVertices, knotSequences, deg, 
				   MFnNurbsCurve::kOpen, false, false, 
				   MObject::kNullObj, &stat);

	if (!stat) {
		stat.perror("Error creating curve");
		return stat;
	}

	stat = curveFn.getPath( path );

	return stat;
}

MStatus helixTool::undoIt()
//
// Description
//     Removes the helix curve from the model.
//
{
	MStatus stat; 
	MObject transform = path.transform();
	stat = MGlobal::removeFromModel( transform );
	return stat;
}

bool helixTool::isUndoable() const
//
// Description
//     Set this command to be undoable.
//
{
	return true;	
}

MStatus helixTool::finalize()
//
// Description
//     Command is finished, construct a string for the command
//     for journalling.
//
{
	MArgList command;
	command.addArg(commandString());
	command.addArg(MString(kRadiusFlag));
	command.addArg(radius);
	command.addArg(MString(kPitchFlag));
	command.addArg(pitch);
	command.addArg(MString(kNumberCVsFlag));
	command.addArg((int) numCV);
	command.addArg(MString(kUpsideDownFlag));
	command.addArg(upDown);
	return MPxToolCommand::doFinalize( command );
}

void helixTool::setRadius(double newRadius)
{
	radius = newRadius;
}

void helixTool::setPitch(double newPitch)
{
	pitch = newPitch;
}

void helixTool::setNumCVs(unsigned newNumCVs)
{
	numCV = newNumCVs;
}

void helixTool::setUpsideDown(bool newUpsideDown)
{
	upDown = newUpsideDown;
}


/////////////////////////////////////////////////////////////
//
// The user Context
//
//   Contexts give the user the ability to write functions
//   for handling events.
//
//   Contexts aren't registered in the plugin, instead a
//   command class (MPxContextCommand) is registered and is used
//   to create instances of the context.
//
/////////////////////////////////////////////////////////////
const char helpString[] = "Click and drag to draw helix";

class helixContext : public MPxContext
{
public:
					helixContext();
	virtual void	toolOnSetup(MEvent &event);
	virtual MStatus doPress(MEvent &event);
	virtual MStatus doDrag(MEvent &event);
	virtual MStatus doRelease(MEvent &event);
	virtual MStatus doEnterRegion(MEvent &event);

	virtual	void	getClassName(MString & name) const;

	void			setNumCVs(unsigned newNumCVs);
	void			setUpsideDown(bool newUpsideDown);
	unsigned		numCVs();
	bool			upsideDown();

private:
	short			startPos_x, startPos_y;
	short			endPos_x, endPos_y;
	unsigned		numCV;
	bool			upDown;
	M3dView			view;
	GLdouble		height,radius;
	
};

helixContext::helixContext() 
{
	numCV = 20;
	upDown = false;
	setTitleString("Helix Tool");

	setCursor( MCursor::defaultCursor );

	// Tell the context which XPM to use so the tool can properly
	// be a candidate for the 6th position on the mini-bar.
	setImage("helixTool.xpm", MPxContext::kImage1 );
}

void helixContext::toolOnSetup(MEvent &)
{
	setHelpString(helpString);
}

MStatus helixContext::doPress(MEvent &event)
{
    event.getPosition(startPos_x, startPos_y);
    view = M3dView::active3dView();
    view.beginGL();
    view.beginOverlayDrawing();
    return MS::kSuccess;
}

MStatus helixContext::doDrag(MEvent & event)
{
    event.getPosition(endPos_x, endPos_y);
    view.clearOverlayPlane();
    glIndexi(2);

	int upFactor;
	if (upDown) upFactor = 1;
	else upFactor = -1;

    // Draw the guide cylinder
    //
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
        glRotatef(upFactor*90.0f, 1.0f, 0.0f, 0.0f);
        GLUquadricObj *qobj = gluNewQuadric();
        gluQuadricDrawStyle(qobj, GLU_LINE);
        GLdouble factor = (GLdouble)numCV;
        radius = double(abs(endPos_x - startPos_x))/factor + 0.1;
        height = double(abs(endPos_y - startPos_y))/factor + 0.1;
        gluCylinder( qobj, radius, radius, height, 8, 1 );
    glPopMatrix();

#ifdef _WIN32
	SwapBuffers( view.deviceContext() );
#elif defined (OSMac_)
	::aglSwapBuffers( view.display());   
#else
	glXSwapBuffers( view.display(), view.window() );
#endif // _WIN32

	return MS::kSuccess;
}

MStatus helixContext::doRelease( MEvent & )
{
    // Clear the overlay plane & restore from overlay drawing
    //
    view.clearOverlayPlane();
    view.endOverlayDrawing();
    view.endGL();

	helixTool * cmd = (helixTool*)newToolCommand();
	cmd->setPitch( height/numCV );
	cmd->setRadius( radius );
	cmd->setNumCVs( numCV );
	cmd->setUpsideDown( upDown );
	cmd->redoIt();
	cmd->finalize();
	return MS::kSuccess;
}

MStatus helixContext::doEnterRegion( MEvent & )
{
	return setHelpString( helpString );
}

void helixContext::getClassName( MString & name ) const
{
	name.set("helix");
}

void helixContext::setNumCVs( unsigned newNumCVs )
{
	numCV = newNumCVs;
	MToolsInfo::setDirtyFlag(*this);
}

void helixContext::setUpsideDown( bool newUpsideDown )
{
	upDown = newUpsideDown;
	MToolsInfo::setDirtyFlag(*this);
}

unsigned helixContext::numCVs()
{
	return numCV;
}

bool helixContext::upsideDown()
{
	return upDown;
}

/////////////////////////////////////////////////////////////
//
// Context creation command
//
//  This is the command that will be used to create instances
//  of our context.
//
/////////////////////////////////////////////////////////////


class helixContextCmd : public MPxContextCommand
{
public:	
						helixContextCmd();
	virtual	MStatus		doEditFlags();
	virtual MStatus		doQueryFlags();
	virtual MPxContext* makeObj();
	virtual MStatus		appendSyntax();
	static void*		creator();

protected:
    helixContext*		fHelixContext;

};

helixContextCmd::helixContextCmd() {}

MPxContext* helixContextCmd::makeObj()
//
// Description
//    When the context command is executed in maya, this method
//    be used to create a context.
//
{
	fHelixContext = new helixContext();
	return fHelixContext;
}

void* helixContextCmd::creator()
//
// Description
//    This method creates the context command.
//
{
	return new helixContextCmd;
}


MStatus helixContextCmd::doEditFlags()
{
	MStatus status = MS::kSuccess;
	
	MArgParser argData = parser();
	
	if (argData.isFlagSet(kNumberCVsFlag)) {
		unsigned numCVs;
		status = argData.getFlagArgument(kNumberCVsFlag, 0, numCVs);
		if (!status) {
			status.perror("numCVs flag parsing failed.");
			return status;
		}
		fHelixContext->setNumCVs(numCVs);
	}

	if (argData.isFlagSet(kUpsideDownFlag)) {
		bool upsideDown;
		status = argData.getFlagArgument(kUpsideDownFlag, 0, upsideDown);
		if (!status) {
			status.perror("upsideDown flag parsing failed.");
			return status;
		}
		fHelixContext->setUpsideDown(upsideDown);
	}
	
	return MS::kSuccess;
}

MStatus helixContextCmd::doQueryFlags()
{
	MArgParser argData = parser();
	
	if (argData.isFlagSet(kNumberCVsFlag)) {
		setResult((int) fHelixContext->numCVs());
	}
	if (argData.isFlagSet(kUpsideDownFlag)) {
		setResult(fHelixContext->upsideDown());
	}
	
	return MS::kSuccess;
}

MStatus helixContextCmd::appendSyntax()
{
	MSyntax mySyntax = syntax();
	
	if (MS::kSuccess != mySyntax.addFlag(kNumberCVsFlag, kNumberCVsFlagLong,
										 MSyntax::kUnsigned)) {
		return MS::kFailure;
	}
	if (MS::kSuccess != 
		mySyntax.addFlag(kUpsideDownFlag, kUpsideDownFlagLong,
						 MSyntax::kBoolean)) {
		return MS::kFailure;
	}

	return MS::kSuccess;
}

///////////////////////////////////////////////////////////////////////
//
// The following routines are used to register/unregister
// the commands we are creating within Maya
//
///////////////////////////////////////////////////////////////////////
MStatus initializePlugin( MObject obj )
{
	MStatus status;
	MFnPlugin plugin(obj, PLUGIN_COMPANY, "3.0", "Any");

	// Register the context creation command and the tool command 
	// that the helixContext will use.
	// 
	status = plugin.registerContextCommand("helixToolContext",
										   helixContextCmd::creator,
										   "helixToolCmd",
										   helixTool::creator,
										   helixTool::newSyntax);
	if (!status) {
		status.perror("registerContextCommand");
		return status;
	}

	return status;
}

MStatus uninitializePlugin( MObject obj)
{
	MStatus status;
	MFnPlugin plugin( obj );

	// Deregister the tool command and the context creation command
	//
	status = plugin.deregisterContextCommand( "helixToolContext",
											  "helixToolCmd" );
	if (!status) {
		status.perror("deregisterContextCommand");
		return status;
	}

	return status;
}
